package cn.edu.dgut.css.sai.security.oauth2.core.http.converter;

import cn.edu.dgut.css.sai.security.oauth2.client.endpoint.SaiCommonOAuth2AccessTokenResponseClient;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.MapOAuth2AccessTokenResponseConverter;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponseMapConverter;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

import java.io.InputStream;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * QQ OAuth2 登录时使用的 {@link OAuth2AccessTokenResponseHttpMessageConverter}
 *
 * @author Sai
 * @see SaiCommonOAuth2AccessTokenResponseClient
 * @see StreamUtils#copyToString(InputStream, Charset)
 * @see StringUtils#tokenizeToStringArray(String, String)
 * @see URLDecoder#decode(String, String)
 * @see MultiValueMap
 * @see MultiValueMap#toSingleValueMap()
 * @see MapOAuth2AccessTokenResponseConverter
 * @see OAuth2AccessTokenResponseMapConverter
 * @since 1.0.5
 */
public final class SaiQQOAuth2AccessTokenResponseHttpMessageConverter extends OAuth2AccessTokenResponseHttpMessageConverter {

    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    public SaiQQOAuth2AccessTokenResponseHttpMessageConverter() {
        List<MediaType> mediaTypes = new ArrayList<>();
        mediaTypes.addAll(super.getSupportedMediaTypes());
        mediaTypes.addAll(Collections.singletonList(new MediaType("text", "html", Collections.singletonMap("charset", "utf-8"))));
        setSupportedMediaTypes(mediaTypes);
    }

    @Override
    protected OAuth2AccessTokenResponse readInternal(Class<? extends OAuth2AccessTokenResponse> clazz, HttpInputMessage inputMessage) throws HttpMessageNotReadableException {
        try {
            MediaType contentType = inputMessage.getHeaders().getContentType();
            Charset charset = (contentType != null && contentType.getCharset() != null ?
                    contentType.getCharset() : DEFAULT_CHARSET);
            String body = StreamUtils.copyToString(inputMessage.getBody(), charset);

            String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
            MultiValueMap<String, String> result = new LinkedMultiValueMap<>(pairs.length);
            for (String pair : pairs) {
                int idx = pair.indexOf('=');
                if (idx == -1) {
                    result.add(URLDecoder.decode(pair, charset.name()), null);
                } else {
                    String name = URLDecoder.decode(pair.substring(0, idx), charset.name());
                    String value = URLDecoder.decode(pair.substring(idx + 1), charset.name());
                    result.add(name, value);
                }
            }

            result.add(OAuth2ParameterNames.TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER.getValue());

            return this.tokenResponseConverter.convert(result.toSingleValueMap());
        } catch (Exception ex) {
            throw new HttpMessageNotReadableException("An error occurred reading the QQ OAuth 2.0 Access Token Response: " +
                    ex.getMessage(), ex, inputMessage);
        }
    }
}
